﻿using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Moq;
using Rubberduck.Parsing.Rewriter;
using Rubberduck.VBEditor.SafeComWrappers;
using RubberduckTests.Mocks;
using Rubberduck.Refactorings.EncapsulateFieldUseBackingField;
using Rubberduck.Parsing.VBA;
using Rubberduck.Refactorings.EncapsulateField;
using Rubberduck.Parsing.Symbols;
using Rubberduck.Refactorings;
using Rubberduck.SmartIndenter;
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
using Rubberduck.Refactorings.Exceptions;
using Rubberduck.Common;

namespace RubberduckTests.Refactoring.EncapsulateField
{
    /// <summary>
    /// EncapsulateFieldReferenceReplacerUDTFieldTests exclusively tests the reference update aspect
    /// of the EncapsulateFieldRefactoring.  So, refactored code generated by the tests do not include
    /// the encapsulation properties or modification of the target Declaration.  Only the target's IdentifierReferences 
    /// are updated (and checked).
    /// </summary>
    [TestFixture]
    public class EncapsulateFieldReferenceReplacerUDTFieldTests
    {
        private const bool IsReadOnlyEncapsulation = true;
        private const bool IsReadWriteEncapsulation = false;

        [TestCase(true, "TestModule1.MyProperty.Fizz = ")]
        [TestCase(false, "TestModule1.MyProperty.Fizz = ")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PublicTypeAndField_ExternalMemberAccessReference(bool wrapInPrivateUDT, string expected)
        {
            var testTargetTuple = ("targetField", "MyProperty", IsReadWriteEncapsulation);

            var testModuleCode =
$@"
Option Explicit

Public Type TestType
    Fizz As Long
End Type

Public targetField As TestType";

            var declaringModule = ModuleTuple("TestModule1", testModuleCode);

            var procedureModuleReferencingCode =
$@"
Option Explicit

Public Sub Bar()
    TestModule1.targetField.Fizz = 7
End Sub
";
            var referencingModuleStdModule = ModuleTuple(moduleName: "StdModule", procedureModuleReferencingCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule, referencingModuleStdModule);

            var referencingModuleCode = refactoredCode[referencingModuleStdModule.TestModuleName];
            StringAssert.Contains(expected, referencingModuleCode);
        }

        [TestCase(true, " .MyProperty.First = ", " .MyProperty.Second = ")]
        [TestCase(false, " .MyProperty.First = ", " .MyProperty.Second = ")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PublicTypeAndField_ExternalWithMemberAccessReference(bool wrapInPrivateUDT, params string[] expectedContent)
        {
            var testTargetTuple = ("targetField", "MyProperty", IsReadWriteEncapsulation);

            var testModuleCode =
$@"
Public Type TBar
    First As String
    Second As Long
End Type

Public targetField As TBar";

            var declaringModule = (MockVbeBuilder.TestModuleName, testModuleCode, ComponentType.StandardModule);

            var moduleReferencingCode =
$@"Option Explicit

'StdModule referencing the UDT

Public Sub FooBar()
    With {declaringModule.TestModuleName}
        .targetField.First = ""Foo""
        .targetField.Second = 7
    End With
End Sub
";
            var referencingModuleStdModule = ModuleTuple(moduleName: "StdModule", moduleReferencingCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule, referencingModuleStdModule);

            var actualCode = refactoredCode[referencingModuleStdModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, "theClass", "theClass.MyProperty.First = ", "theClass.MyProperty.Second = ", " .MyProperty.Second = ")]
        [TestCase(false, "theClass", "theClass.MyProperty.First = ", "theClass.MyProperty.Second = ", " .MyProperty.Second = ")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PublicTypeAndField_ClassModuleSource_ExternalReferences(bool wrapInPrivateUDT, string classInstanceName, params string[] expectedContent)
        {
            var testTargetTuple = ("targetField", "MyProperty", IsReadWriteEncapsulation);

            var testModuleCode =
$@"
Option Explicit

Public targetField As TBar";
            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode, ComponentType.ClassModule);

            var moduleReferencingCode =
$@"
Option Explicit

Public Type TBar
    First As String
    Second As Long
End Type

Private {classInstanceName} As {declaringModule.TestModuleName}

Public Sub Initialize()
    Set {classInstanceName} = New {declaringModule.TestModuleName}
End Sub

Public Sub Fizz()
    {classInstanceName}.targetField.First = ""Foo""
End Sub

Public Sub Bang()
    {classInstanceName}.targetField.Second = 7
End Sub

Public Sub FizzBang()
    With {classInstanceName}
        .targetField.First = ""FizzBang""
        .targetField.Second = 7
    End With
End Sub
";
            var referencingModuleStdModule = ModuleTuple(moduleName: "StdModule", moduleReferencingCode);
                        
            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule, referencingModuleStdModule);
            
            var actualCode = refactoredCode[referencingModuleStdModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, "this.MyBazz.FirstValue = newValue", "this.MyBazz.SecondValue = newValue")]
        [TestCase(false, "myBazz.FirstValue = newValue", "myBazz.SecondValue = newValue")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PrivateTypeAndField_LocalAssignmentReferences_ReadOnly(bool wrapInPrivateUDT, params string[] expectedContent)
        {
            var testTargetTuple = ("myBazz", "MyBazz", IsReadOnlyEncapsulation);

            var testModuleCode =
$@"
Private Type TBazz
    FirstValue As String
    SecondValue As Long
End Type

Private myBazz As TBazz

Public Sub Fizz(newValue As String)
    myBazz.FirstValue = newValue
End Sub

Public Sub Bazz(newValue As Long)
    myBazz.SecondValue = newValue
End Sub
";
            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule);

            var actualCode = refactoredCode[declaringModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, "  FirstValue = newValue", "  SecondValue = newValue")]
        [TestCase(false, "  FirstValue = newValue", "  SecondValue = newValue")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PrivateTypeAndField_LocalAssignmentReferences_ReadWrite(bool wrapInPrivateUDT, params string[] expectedContent)
        {
            var testTargetTuple = ("myBazz", "MyBazz", IsReadWriteEncapsulation);

            var testModuleCode =
 $@"
Private Type TBazz
    FirstValue As String
    SecondValue As Long
End Type

Private myBazz As TBazz

Public Sub Fizz(newValue As String)
    myBazz.FirstValue = newValue
End Sub

Public Sub Bazz(newValue As Long)
    myBazz.SecondValue = newValue
End Sub
";
            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule);

            var actualCode = refactoredCode[declaringModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, " With this.MyBazz", " .FirstValue = newValue", " .SecondValue = newValue")]
        [TestCase(false, " With myBazz", " .FirstValue = newValue", " .SecondValue = newValue")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PrivateTypeAndField__LocalWithMemberAccess_ReadOnly(bool wrapInPrivateUDT, params string[] expectedContent)
        {
            var testTargetTuple = ("myBazz", "MyBazz", IsReadOnlyEncapsulation);

            var testModuleCode =
$@"
Private Type TBazz
    FirstValue As String
    SecondValue As Long
End Type

Private myBazz As TBazz

Public Sub Fizz(newValue As String)
    With myBazz
        .FirstValue = newValue
    End With
End Sub

Public Sub Bazz(newValue As String)
    With myBazz
        .SecondValue = newValue
    End With
End Sub
";
            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule);

            var actualCode = refactoredCode[declaringModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, "  With this.MyBazz", " FirstValue = newValue", " SecondValue = newValue")]
        [TestCase(false, "  With myBazz", " FirstValue = newValue", " SecondValue = newValue")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PrivateTypeAndField__LocalWithMemberAccess_ReadWrite(bool wrapInPrivateUDT, params string[] expectedContent)
        {
            var testTargetTuple = ("myBazz", "MyBazz", IsReadWriteEncapsulation);

            var testModuleCode =
$@"
Private Type TBazz
    FirstValue As String
    SecondValue As Long
End Type

Private myBazz As TBazz

Public Sub Fizz(newValue As String)
    With myBazz
        .FirstValue = newValue
    End With
End Sub

Public Sub Bazz(newValue As String)
    With myBazz
        .SecondValue = newValue
    End With
End Sub
";
            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule);

            //Note: The EF refactoring will create a FirstValue and SecondValue property - so the with member access expression
            //is replaced with the Let property name. The EF refactoring does not remove the 'With' statement block even 
            //though it is no longer required by this specific scenario
            var actualCode = refactoredCode[declaringModule.TestModuleName];

            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, "FirstValue = arg", "GetTheFirstValue = FirstValue")]
        [TestCase(false, "FirstValue = arg", "GetTheFirstValue = FirstValue")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PrivateTypeAndField_LocalReadAndWriteAccessors_ReadWrite(bool wrapInPrivateUDT, params string[] expectedContent)
        {
            var testTargetTuple = ("myBazz", "MyBazz", IsReadWriteEncapsulation);

            var testModuleCode =
$@"
Private Type TBazz
    FirstValue As String
    SecondValue As Long
End Type

Private myBazz As TBazz

Public Function GetTheFirstValue() As String
    GetTheFirstValue = myBazz.FirstValue
End Function

Public Sub SetTheFirstValue(arg As Long)
    myBazz.FirstValue = arg
End Sub
";
            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule);

            var actualCode = refactoredCode[declaringModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, "this.MyBazz.FirstValue = arg", "GetTheFirstValue = FirstValue")]
        [TestCase(false, "myBazz.FirstValue = arg", "GetTheFirstValue = FirstValue")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PrivateTypeAndField_LocalReadAndWriteAccessors_ReadOnly(bool wrapInPrivateUDT, params string[] expectedContent)
        {
            var testTargetTuple = ("myBazz", "MyBazz", IsReadOnlyEncapsulation);

            var testModuleCode =
$@"
Private Type TBazz
    FirstValue As String
    SecondValue As Long
End Type

Private myBazz As TBazz

Public Function GetTheFirstValue() As String
    GetTheFirstValue = myBazz.FirstValue
End Function

Public Sub SetTheFirstValue(arg As Long)
    myBazz.FirstValue = arg
End Sub
";
            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule);

            var actualCode = refactoredCode[declaringModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, "GetBazzFirst = TestModule1.MyProperty.FirstValue", "TestModule1.MyProperty.FirstValue = arg")]
        [TestCase(false, "GetBazzFirst = TestModule1.MyProperty.FirstValue", "TestModule1.MyProperty.FirstValue = arg")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PublicTypeAndField_ExternalReadAndWriteExpressions(bool wrapInPrivateUDT, params string[] expectedContent)
        {
            var testTargetTuple = ("myBazz", "MyProperty", IsReadWriteEncapsulation);

            var testModuleCode =
$@"

Public Type TBazz
    FirstValue As String
    SecondValue As Long
End Type

Public myBazz As TBazz
";
            var declaringModule = ModuleTuple("TestModule1", testModuleCode);

            var referencingModule = "AnotherModule";
            var referencingModuleCode =
$@"

Public Function GetBazzFirst() As String
    GetBazzFirst = myBazz.FirstValue
End Function

Public Sub SetBazzFirst(arg As Long)
    myBazz.FirstValue = arg
End Sub
";
            var referencingModuleStdModule = ModuleTuple(moduleName: referencingModule, referencingModuleCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule, referencingModuleStdModule);

            var actualCode = refactoredCode[referencingModuleStdModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, " First = arg1", " Second = arg2", " bogeyField.First = arg1", " bogeyField.Second = arg2")]
        [TestCase(false, " First = arg1", " Second = arg2", " bogeyField.First = arg1", " bogeyField.Second = arg2")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PrivateTypeAndField_ModifiesCorrectUDTMemberReferencess_ReadWrite(bool wrapInPrivateUDT, params string[] expectedContent)
        {
            var testTargetTuple = ("targetField", "TargetField", IsReadWriteEncapsulation);

            var testModuleCode =
$@"
Private Type TBar
    First As String
    Second As Long
End Type

Private targetField As TBar

Private bogeyField As TBar

Public Sub Foo(arg1 As String, arg2 As Long)
    targetField.First = arg1
    bogeyField.First = arg1
    targetField.Second = arg2
    bogeyField.Second = arg2
End Sub
";
            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule);

            var actualCode = refactoredCode[declaringModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, " this.TargetField.First = arg1", " this.TargetField.Second = arg2", " bogeyField.First = arg1", " bogeyField.Second = arg2")]
        [TestCase(false, " targetField.First = arg1", " targetField.Second = arg2", " bogeyField.First = arg1", " bogeyField.Second = arg2")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PrivateTypeAndField_ModifiesCorrectUDTMemberReferences_ReadOnly(bool wrapInPrivateUDT, params string[] expectedContent)
        {
            var testTargetTuple = ("targetField", "TargetField", IsReadOnlyEncapsulation);

            var testModuleCode =
$@"
Private Type TBar
    First As String
    Second As Long
End Type

Private targetField As TBar

Private bogeyField As TBar

Public Sub Foo(arg1 As String, arg2 As Long)
    targetField.First = arg1
    bogeyField.First = arg1
    targetField.Second = arg2
    bogeyField.Second = arg2
End Sub
";
            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule);

            var actualCode = refactoredCode[declaringModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, "SecondValType1 = \"Wah\"", "TestSub3 SecondValType1")]
        [TestCase(false, "SecondValType1 = \"Wah\"", "TestSub3 SecondValType1")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PrivateTypeAndField_NestedUDTMembers_ReadWrite(bool wrapInPrivateUDT, params string[] expectedContent)
        {
            var testTargetTuple = ("mTypesField", "TypesField", IsReadWriteEncapsulation);

            var testModuleCode =
$@"
Option Explicit

Private Type PType1
    FirstValType1 As Long
    SecondValType1 As String
End Type

Private Type PType2
    FirstValType2 As Long
    SecondValType2 As String
    Third As PType1
End Type

Private mTypesField As PType2

Private Sub Class_Initialize()
    mTypesField.Third.SecondValType1 = ""Wah""
End Sub

Private Sub TestSub2()
    TestSub3 mTypesField.Third.SecondValType1
End Sub

Private Sub TestSub3(ByVal arg As String)
End Sub
";
            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule);

            var actualCode = refactoredCode[declaringModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, "this.TypesField.Third.SecondValType1 = \"Wah\"", "TestSub3 SecondValType1")]
        [TestCase(false, "TypesField.Third.SecondValType1 = \"Wah\"", "TestSub3 SecondValType1")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PrivateTypeAndField_NestedUDTMembers_ReadOnlys(bool wrapInPrivateUDT, params string[] expectedContent)
        {
            var testTargetTuple = ("mTypesField", "TypesField", IsReadOnlyEncapsulation);

            var testModuleCode =
$@"
Option Explicit

Private Type PType1
    FirstValType1 As Long
    SecondValType1 As String
End Type

Private Type PType2
    FirstValType2 As Long
    SecondValType2 As String
    Third As PType1
End Type

Private mTypesField As PType2

Private Sub Class_Initialize()
    mTypesField.Third.SecondValType1 = ""Wah""
End Sub

Private Sub TestSub2()
    TestSub3 mTypesField.Third.SecondValType1
End Sub

Private Sub TestSub3(ByVal arg As String)
End Sub
";
            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule);

            var actualCode = refactoredCode[declaringModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, "With .TypesField", " testVal = .FirstValType1")]
        [TestCase(false, "With .TypesField", " testVal = .FirstValType1")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PublicTypeAndField_ExternalRefNestedWithStatement(bool wrapInPrivateUDT, params string[] expectedContent)
        {
            var testTargetTuple = ("mTypesField", "TypesField", IsReadWriteEncapsulation);

            var testModuleCode =
$@"
Option Explicit

Public Type PType1
    FirstValType1 As Long
End Type

Public Type PType2
    Third As PType1
End Type

Public mTypesField As PType2
";

            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode);

            var referencingModuleName = "AnotherModule";
            var referencingModuleCode =
$@"
Private testVal As Long

Public Sub TestSub()
    With {declaringModule.TestModuleName}
        With .mTypesField
            With .Third
                testVal = .FirstValType1
            End With
        End With
    End With
End Sub
";
            var referencingModuleStdModule = ModuleTuple(moduleName: referencingModuleName, referencingModuleCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule, referencingModuleStdModule);

            var actualCode = refactoredCode[referencingModuleStdModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, "With this.TypesField", "With .Third", "FirstValType1 = arg", "TestFunc = FirstValType1")]
        [TestCase(false, "With mTypesField", "With .Third", "FirstValType1 = arg", "TestFunc = FirstValType1")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PrivateTypeAndField_RefNestedWithStatements_ReadWrite(bool wrapInPrivateUDT, params string[] expectedContent)
        {
            var testTargetTuple = ("mTypesField", "TypesField", IsReadWriteEncapsulation);

            var testModuleCode =
$@"
Option Explicit

Private Type PType1
    FirstValType1 As Long
End Type

Private Type PType2
    Third As PType1
End Type

Private mTypesField As PType2

Public Sub TestSub(ByVal arg As Long)
    With mTypesField
        With .Third
            .FirstValType1 = arg
        End With
    End With
End Sub

Public Function TestFunc() As Long
    With mTypesField
        With .Third
            TestFunc = .FirstValType1
        End With
    End With
End Function

";
            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule);

            var actualCode = refactoredCode[declaringModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, "With this.TypesField", "With .Third", ".FirstValType1 = arg", "TestFunc = FirstValType1")]
        [TestCase(false, "With mTypesField", "With .Third", ".FirstValType1 = arg", "TestFunc = FirstValType1")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PrivateTypeAndField_RefNestedWithStatements_ReadOnly(bool wrapInPrivateUDT, params string[] expectedContent)
        {
            var testTargetTuple = ("mTypesField", "TypesField", IsReadOnlyEncapsulation);

            var testModuleCode =
$@"
Option Explicit

Private Type PType1
    FirstValType1 As Long
End Type

Private Type PType2
    Third As PType1
End Type

Private mTypesField As PType2

Public Sub TestSub(ByVal arg As Long)
    With mTypesField
        With .Third
            .FirstValType1 = arg
        End With
    End With
End Sub

Public Function TestFunc() As Long
    With mTypesField
        With .Third
            TestFunc = .FirstValType1
        End With
    End With
End Function

";

            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule);

            var actualCode = refactoredCode[declaringModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, " Wheels = 4", " Seats = 2")]
        [TestCase(false, " Wheels = 4", " Seats = 2")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PrivateTypeAndField_MultipleMembers_ReadWrite(bool wrapInPrivateUDT, params string[] expectedContent)
        {            
            var testTargetTuple = ("mVehicle", "Vehicle", IsReadWriteEncapsulation);
            
            var testModuleCode =
$@"
Option Explicit

Private Type TVehicle
    Seats As Integer
    Wheels As Integer
End Type

Private mVehicle As TVehicle

Private Sub Class_Initialize()
    mVehicle.Wheels = 4
    mVehicle.Seats = 2
End Sub
";

            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode);
            
            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule);
            var actualCode = refactoredCode[declaringModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, " this.Vehicle.Wheels = 4", " this.Vehicle.Seats = 2")]
        [TestCase(false, " mVehicle.Wheels = 4", " mVehicle.Seats = 2")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PrivateTypeAndField_MultipleMembers_ReadOnly(bool wrapInPrivateUDT, params string[] expectedContent)
        {
            var testTargetTuple = ("mVehicle", "Vehicle", IsReadOnlyEncapsulation);

            var testModuleCode =
$@"
Option Explicit

Private Type TVehicle
    Seats As Integer
    Wheels As Integer
End Type

Private mVehicle As TVehicle

Private Sub Class_Initialize()
    mVehicle.Wheels = 4
    mVehicle.Seats = 2
End Sub
";
            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule);
            var actualCode = refactoredCode[declaringModule.TestModuleName];
            foreach (var fragment in expectedContent)
            {
                StringAssert.Contains(fragment, actualCode);
            }
        }

        [TestCase(true, "With TestModule1.Fizz")]
        [TestCase(false, "With TestModule1.Fizz")]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void PublicTypeAndField_ModifiesCorrectExternalUDTFieldReference(bool wrapInPrivateUDT, string expected)
        {
            var testTargetTuple = ("Fizz", "Fizz", IsReadWriteEncapsulation);

            var testModuleCode =
$@"
Public Type TBar
    First As String
    Second As Long
End Type

Public Fizz As TBar

Public Bazz As TBar
";
            var declaringModule = ModuleTuple("TestModule1", testModuleCode);

            var referencingModuleName = "AnotherModule";
            var referencingModuleCode =
$@"
Public Sub Foo(arg1 As String, arg2 As Long)
    With {declaringModule.TestModuleName}.Fizz
        .First = arg1
        .Second = arg2
    End With

    With Bazz
        .First = arg1
        .Second = arg2
    End With
End Sub
";
            var referencingModuleStdModule = (moduleName: referencingModuleName, referencingModuleCode, ComponentType.StandardModule);

            var refactoredCode = ReplaceReferences(wrapInPrivateUDT, testTargetTuple, declaringModule, referencingModuleStdModule);
            var actualCode = refactoredCode[referencingModuleName];
            
            StringAssert.Contains(expected, actualCode);
        }

        [TestCase(true)]
        [TestCase(false)]
        [Category("Refactorings")]
        [Category("Encapsulate Field")]
        [Category(nameof(EncapsulateFieldReferenceReplacer))]
        public void ThrowsOnInfiniteNestedUDTs(bool wrapInPrivateUDT)
        {
            var testTargetTuple = ("mBazz", "Bazz", IsReadWriteEncapsulation);

            var testModuleCode =
$@"
Public Type TBar
    First As String
    Second As Long
End Type

Public mBazz As TBar
";
            var declaringModule = ModuleTuple(MockVbeBuilder.TestModuleName, testModuleCode);

            var vbe = MockVbeBuilder.BuildFromModules(declaringModule);
            (var state, var rewritingManager) = MockParser.CreateAndParseWithRewritingManager(vbe.Object);
            using (state)
            {
                var support = new EncapsulateFieldTestSupport();

                var resolver = support.SetupResolver(state, rewritingManager);

                var fieldDeclaration = state.DeclarationFinder.MatchName(testTargetTuple.Item1).Single();

                var encapsulateFieldFactory = resolver.Resolve<IEncapsulateFieldCandidateFactory>();
                var fieldCandidate = encapsulateFieldFactory.CreateFieldCandidate(fieldDeclaration);

                if (wrapInPrivateUDT)
                {
                    var defaultObjectStateUDT = encapsulateFieldFactory.CreateDefaultObjectStateField(fieldDeclaration.QualifiedModuleName);
                    fieldCandidate = encapsulateFieldFactory.CreateUDTMemberCandidate(fieldCandidate, defaultObjectStateUDT);
                }

                fieldCandidate.PropertyIdentifier = testTargetTuple.Item2;
                fieldCandidate.IsReadOnly = testTargetTuple.Item3;
                fieldCandidate.EncapsulateFlag = true;

                var selectedFieldDeclarations = (new List<IEncapsulateFieldCandidate>() { fieldCandidate }).Select(f => f.Declaration).Cast<VariableDeclaration>();

                var provider = resolver.Resolve<IUDTMemberReferenceProvider>();
                Assert.Throws<ArgumentException>(() => provider.UdtFieldToMemberReferences(state, selectedFieldDeclarations, (udtMembers) => true));
            }
        }

        private static (string TestModuleName, string TargetID, ComponentType ComponentType) ModuleTuple(string moduleName, string targetName, ComponentType componentType = ComponentType.StandardModule)
            => (moduleName, targetName, componentType);

        private static IDictionary<string, string> ReplaceReferences(bool wrapInPrivateUDT, (string, string, bool) testTargetTuple, params (string, string, ComponentType)[] moduleTuples)
        {
            return ReferenceReplacerTestSupport.TestReferenceReplacement(wrapInPrivateUDT, testTargetTuple, moduleTuples);
        }
    }
}
